package meshToSolid;

import java.awt.geom.GeneralPath;
import java.io.File;
import java.util.Arrays;
import java.util.Vector;

import grid.Grid3DFloat;
import hdgeom.bezier.Bezier;
import hdgeom.primitives.Box;
import mmesh.MMesh;
import solid.SimpleMCube;
import peasy.PeasyCam;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PVector;
import simpleGeom.Polygon2D;
import simpleGui.*;

public class Copy_2_of_Voxelization extends PApplet {
	String path = "/Users/dillenburger_b/Documents/Projects/2014 Grotto2/hq/";
	//String pathObject = path + "120acSolid.obj";
	String pathObject = path + "120acSolid.obj";
	
	String pathValue = pathObject.replace(".obj", ".txt");
	String pathMCubeOut =  pathObject.replace(".obj", "vox.obj");

	boolean scanForm = false;
	float dimGrid = 0.005f;// size of voxels in meters

	MMesh mesh;
	Box boundsObject; // Boundingbox of objectfile
	Box boundsInside; // Boundingbox defining voxelized part
	SimpleMCube mc = new SimpleMCube();
	SimpleMCube mcPreview = new SimpleMCube();

	//PCamera3D cam;
	Grid3DFloat grid;
	int displayMode = 0;
	Gui gui;
	GuiSpinnerGroup sx1, sx2, sy1, sy2, sz1, sz2;
	
	Grid3DFloat gridPreview;

	boolean flagUpdateInsideOutside;
	boolean flagExport=false;
	float posX, posY, posZ = 0;
	
	//float iso=dimGrid*0.85f;
	float isoFactor=0.85f;
	float iso=dimGrid*isoFactor;
	PeasyCam cam;

	float rY=0;
	int step=4;


	Box crop=new Box(-1.97f,-2.00f, 0.02f,0.02f,1.65f,2.9f);
	//Box crop=new Box(-1.4f,-0.8f, 0.3f,-0.6f,0.4f,1f);

	
	public void setup() {
		//
		pathValue="/Users/dillenburger_b/Documents/Projects/2014 Grotto2/hq/values20140615-090259_dim0.0050.txt";
		size(1000, 600, P3D);
		setupGui();
		cam = new PeasyCam(this,500);
		//loadGrid(pathValue);
		//scanContinues("/Users/dillenburger_b/Documents/Projects/2014 Grotto2/hq/michael02/");
		
		voxelize(scanForm);
	}
	public void setupGui() {
		int py = 20;
		int pyO = 10;
		int x1 = 10;
		int x2 = 30;
		gui = new Gui(this, this.createFont("Arial", 12));
		gui.addGuiLabel(x1, py + pyO, "x1x2: ");
		sx1 = gui.addGuiSpinnerGroup(x2, py, crop.x1,-3f, 0.2f,4,3);
		py += 25;
		sx2 = gui.addGuiSpinnerGroup(x2, py, crop.x2,-3,0.2f, 4,3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "y1: ");
		sy1 = gui.addGuiSpinnerGroup(x2, py, crop.y1,-3,3, 4,3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "y2: ");
		sy2 = gui.addGuiSpinnerGroup(x2, py, crop.y2,-3,3, 4,3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "z1: ");
		sz1 = gui.addGuiSpinnerGroup(x2, py, crop.z1,-1,4, 4,3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "z2: ");
		sz2 = gui.addGuiSpinnerGroup(x2, py, crop.z2,-1,4, 4,3);

	}
	
	
	public void loadOBJ() {
		mesh = new MMesh();
		mesh.loadOBJ(pathObject);
		mesh.triangulateQuads();
		boundsObject = mesh.getBounds();
		boundsObject.y2=boundsObject.getDimY()/3+boundsObject.y1;
		println("bounds: "+boundsObject);
		boundsObject = boundsObject.getOffsetBox(dimGrid * 2);
	}

	public void voxelize(boolean doScan) {
		if (doScan) {
			loadOBJ();
			scan(dimGrid);
			
			grid.saveValues(pathValue);
		} else {
			//loadOBJ();
		}
		posZ=crop.z1+0.02f;
		posY=0;
		posX=0;
		defineInsideOutside(posX, posY, posZ);
		if (!flagExport){
		mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values,
				grid.dim * 0.85f, grid.dim, grid.x1, grid.y1, grid.z1, null);
		}
		else{
			mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values,
					grid.dim * 0.85f, grid.dim, grid.x1, grid.y1, grid.z1, path+timeStamp()+".obj");
			flagExport=false;
		}
	}

	public void scan(float dim) {
		float dimX = boundsObject.getDimX();
		float dimY = boundsObject.getDimY();
		float dimZ = boundsObject.getDimZ();
		int nX = (int) (dimX / dim) + 2;
		int nY = (int) (dimY / dim) + 2;
		int nZ = (int) (dimZ / dim) + 2;
		println("nX: " + nX + "  nY: " + nY + "  nZ: " + nZ);
		grid = new Grid3DFloat(nX, nY, nZ);
		grid.x1 = boundsObject.x1;
		grid.y1 = boundsObject.y1;
		grid.z1 = boundsObject.z1;
		grid.dim = dim;
		// scanning
		MMesh.scanMeshDistance(mesh.xc, mesh.yc, mesh.zc, mesh.triangles,
				boundsObject.x1, boundsObject.y1, boundsObject.z1, nX, nY, nZ,
				dim, dim * 1.6f, grid.values);
	}
	public void constructScanBounds(){
		//bounds: x1:-2.22912 y1:-2.89137 z1:-0.11573 dX:2.35061 dY:1.6964 dZ:2.97742
		//new Box(-1.95f,-2.00f, -0.00f,0.02f,1.65f,2.84f);
		boundsObject=new Box();
		
		boundsObject.x1=-2.1f;
		boundsObject.y1=-2.2f;
		boundsObject.z1=-0.11573f;
		boundsObject.x2=0.05f;
		boundsObject.y2=1.75f;
		boundsObject.z2=3.05f;
		/*boundsObject.x1*=1000;
		boundsObject.x2*=1000;
		boundsObject.y1*=1000;
		boundsObject.y2*=1000;
		boundsObject.z1*=1000;
		boundsObject.z2*=1000;*/
		boundsObject.offset(dimGrid*2f);
	}
	
	public Box constructInsideBounds(){
		return new Box(
				grid.getXInt(sx1.getFloatValue()), 
				grid.getYInt(sy1.getFloatValue()),
				grid.getZInt(sz1.getFloatValue()),
				grid.getXInt(sx2.getFloatValue())+1, 
				grid.getYInt(sy2.getFloatValue())+1,
				grid.getZInt(sz2.getFloatValue())+1
				);
	}
	public void loadGrid(String fileName){
		grid=new Grid3DFloat(fileName);
		
	}
	public void scanContinues(String folderPath){
		constructScanBounds();
		float dimX = boundsObject.getDimX();
		float dimY = boundsObject.getDimY();
		float dimZ = boundsObject.getDimZ();
		int nX = (int) (dimX / dimGrid) + 2;
		int nY = (int) (dimY / dimGrid) + 2;
		int nZ = (int) (dimZ / dimGrid) + 2;
		println("nX: " + nX + "  nY: " + nY + "  nZ: " + nZ);
		grid = new Grid3DFloat(nX, nY, nZ);
		grid.x1 = boundsObject.x1;
		grid.y1 = boundsObject.y1;
		grid.z1 = boundsObject.z1;
		grid.dim = dimGrid;
		Arrays.fill(grid.values, 100000);
		scanContinues(folderPath,grid);
	}
	public void scanContinues(String folderPath, Grid3DFloat grid){
		Vector<File> files = FileHelper.getFilesFromFolder(folderPath, ".obj");
		for (int i = 0; i < files.size(); i++) {
			File file = files.get(i);
			mesh = new MMesh();
			mesh.loadOBJ(file.getAbsolutePath());
			//mesh.scale(1000, 1000, 1000);
			println("file:" + i + "/" + files.size() + " name"
					+ file.getAbsolutePath());
			mesh.triangulateQuads();
			
			MMesh.scanMeshDistanceContinue(mesh.xc, mesh.yc, mesh.zc, mesh.triangles,
					boundsObject.x1, boundsObject.y1, boundsObject.z1, grid.nX, grid.nY, grid.nZ,
					grid.dim, grid.dim * 1.6f, grid.values);
		}
		grid.saveValues(folderPath+"/values"+timeStamp()+"_dim"+grid.dim+".txt");
	}
	public void initPreviewGrid(){
		
		gridPreview=new Grid3DFloat(grid.nX/step,grid.nY/step,grid.nZ/step);
		gridPreview.x1=grid.x1;
		gridPreview.y1=grid.y1;
		gridPreview.z1=grid.z1;
		gridPreview.dim=grid.dim*step;
		
	}
	public void loadPreviewGrid(){
		for (int x=0;x<gridPreview.nX;x++){
			for (int y=0;y<gridPreview.nY;y++){
				for (int z=0;z<gridPreview.nZ;z++){
					gridPreview.set(x,y,z,grid.get(x*step, y*step, z*step));
				}
			}
		}
		/*for (int x=0;x<gridPreview.nX;x++){
			for (int y=0;y<gridPreview.nY;y++){
				for (int z=0;z<gridPreview.nZ;z++){
					gridPreview.set(x,y,z,grid.get(x*step, y*step, z*step));
				}
			}
		}*/
		
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boundsInside.x1 > x || boundsInside.x2 < x
							|| boundsInside.y1 > y || boundsInside.y2 < y
							|| boundsInside.z1 > z || boundsInside.z2 < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = grid.dim * 2;
					}
				}
			}
		}
		
		mcPreview.marchingCubes(gridPreview.nX, gridPreview.nY, gridPreview.nZ, gridPreview.values,
				iso, gridPreview.dim, gridPreview.x1, gridPreview.y1, gridPreview.z1, null);
	}
	
	public void loadBoundingBox(String folderPath) {
		Box b = new Box();
		Vector<File> files = FileHelper.getFilesFromFolder(folderPath, ".obj");
		for (int i = 0; i < files.size(); i++) {
			File file = files.get(i);
			mesh = new MMesh();
			mesh.loadOBJ(file.getAbsolutePath());
			println("file:" + i + "/" + files.size() + " name"
					+ file.getAbsolutePath());
			mesh.triangulateQuads();
			if (i == 0) {
				b = mesh.getBounds();
			} else {
				b.add(mesh.getBounds());
			}
		}
		println(b);
	}
	public void defineInsideOutside(float xPos, float yPos, float zPos) {
		
		
		
				
			
		grid = new Grid3DFloat(pathValue);
		println("dim: "+grid.dim);
		boundsInside = constructInsideBounds();

		println("grid pt1: "+grid.x1()+" "+grid.y1()+" "+grid.z1() );
		println("grid pt2: "+grid.x2()+" "+grid.y2()+" "+grid.z2() );
		initPreviewGrid();
		println(" grid dim: "+grid.x1+" "+grid.y1+" "+grid.z1);
	//	println(" grid dim: ");
		println(" pos: "+posX+" "+posY+" "+posZ);
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boundsInside.x1 > x || boundsInside.x2 < x
							|| boundsInside.y1 > y || boundsInside.y2 < y
							|| boundsInside.z1 > z || boundsInside.z2 < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = 0;
					} else {
						if (grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] < 0) {
							grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] *= -1;
						}
					}
				}
			}
		}
		
		

		println("start reach");
		println("grid info");
		
		int iX = grid.getXInt(xPos);
		int iY = grid.getYInt(yPos);
		int iZ = grid.getZInt(zPos);
		println(" pos int: "+iX+" "+iY+" "+iZ);
		println("grid."+grid.nX+" "+grid.nY+" "+grid.nZ);
		int index = grid.getIndex(iX, iY, iZ);
		grid.reached(index, iso);
		/*Grid3DFloat.reached(grid.values, grid.nX, grid.nY, grid.nZ, index,
				iso);*/
		println("end reach");
		//negative reached
		for (int i = 0; i < grid.values.length; i++) {
			if (grid.values[i] < 0)
				grid.values[i] *= -1;
			else if (grid.values[i] > iso)
				grid.values[i] = 0;
		}

		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boundsInside.x1 > x || boundsInside.x2 < x
							|| boundsInside.y1 > y || boundsInside.y2 < y
							|| boundsInside.z1 > z || boundsInside.z2 < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = grid.dim * 2;
					}
				}
			}
		}
		//smaller iso->inside
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (grid.get(x, y, z)<iso){
					if (boundsInside.x1 == x || boundsInside.x2 == x
							|| boundsInside.y1 == y || boundsInside.y2 ==y
							|| boundsInside.z1 == z || boundsInside.z2 == z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = 0;
					}
					}
				}
			}
		}
		crop();
		loadPreviewGrid();
	}
	Polygon2D border;
	public void crop(){
		//if (outsideCrop) distance to line
		//if (insideCrop and unreached)
		
		//smaller iso->inside
		float zM=1.5f;
		border=new Polygon2D();
		border.addPt(sx2.getFloatValue(),sz2.getFloatValue());
		border.addPt(sx2.getFloatValue(),sz1.getFloatValue());
		border.addPt(sx1.getFloatValue(),sz1.getFloatValue());
		//border.addPt(sx1.getFloatValue(),zM);
		Bezier bez=new Bezier();
		bez.addControlPoint(sx1.getFloatValue(),zM,0);
		bez.addControlPoint(sx1.getFloatValue(),zM+1,0);
		bez.addControlPoint(sx2.getFloatValue()-1,sz2.getFloatValue(),0);
		bez.addControlPoint(sx2.getFloatValue(),sz2.getFloatValue(),0);
		int nSegs=100;
		for (int i=0;i<nSegs;i++){
			PVector p=bez.getPoint(i*1f/nSegs);
			border.addPt(p.x, p.y);
		}
		
		border=new Polygon2D();
		border.addPt(sx2.getFloatValue(),sz2.getFloatValue());
		border.addPt(sx2.getFloatValue(),sz1.getFloatValue());
		//border.addPt(sx1.getFloatValue(),sz1.getFloatValue());
		//border.addPt(sx1.getFloatValue(),zM);
		 bez=new Bezier();
		bez.addControlPoint(sx1.getFloatValue(),sz1.getFloatValue(),0);
		bez.addControlPoint(sx1.getFloatValue(),sz2.getFloatValue(),0);
		bez.addControlPoint(sx1.getFloatValue(),sz2.getFloatValue(),0);
		//bez.addControlPoint(sx1.getFloatValue()-1,sz2.getFloatValue(),0);
		bez.addControlPoint(sx2.getFloatValue(),sz2.getFloatValue(),0);
		 nSegs=100;
		for (int i=0;i<nSegs;i++){
			PVector p=bez.getPoint(i*1f/nSegs);
			border.addPt(p.x, p.y);
		}
		GeneralPath area=border.getGeneralPath();
		
		float[][] inoutLayer=new float[grid.nX][grid.nZ];
		for (int x=0;x<grid.nX;x++){
			for (int z=0;z<grid.nZ;z++){
				float xP=grid.getXFloat(x);
				float yP=grid.getZFloat(z);
				float d=border.getClosestDist(xP, yP);
				if (!area.contains(xP, yP)){
					inoutLayer[x][z]=border.getClosestDist(xP, yP);
				}
			}
		}
		
		//cropping everything that is outside
		for (int y=0;y<grid.nY;y++){
			for (int x=0;x<grid.nX;x++){
				for (int z=0;z<grid.nZ;z++){
					if (grid.get(x,y,z)<iso){
					if (inoutLayer[x][z]>0){
						grid.set(x, y, z, inoutLayer[x][z]);
					}
					}
				}
			}
		}
	}
	
	public void saveSegments(){
		Box box=constructInsideBounds();
		int nSegs=6;
		int nY=(int)box.getDimY()/nSegs;
		Grid3DFloat seg=new Grid3DFloat(grid.nX,nY,grid.nZ);
		seg.x1=grid.x1;
		seg.z1=grid.z1;
		seg.dim=grid.dim;
		for (int i=0;i<nSegs;i++){
			int y1=i*nY+(int)box.y1;
			int y2=(i+1)*nY+(int)box.y1;
			seg.y1=grid.getYFloat(y1);
			for (int x=0;x<grid.nX;x++){
				for (int z=0;z<grid.nZ;z++){
					
					for (int cY=y1;cY<y2;cY++){
						
						seg.set(x, cY-y1, z, grid.get(x,cY,z));
						if(cY==y1||cY==y2-1){
							seg.set(x, cY-y1, z, iso*2);
						}
					}
				}
			}
			
			mc.marchingCubes(seg.nX, seg.nY, seg.nZ, seg.values,
					seg.dim * 0.85f, seg.dim, seg.x1, seg.y1, seg.z1, path+"seg"+i+"-"+timeStamp()+".obj");
		}
		
	}
	public void draw() {
		
		boundsInside = constructInsideBounds();
		if (flagUpdateInsideOutside) {
			flagUpdateInsideOutside = false;
			posZ=sz1.getFloatValue()+0.02f;
			posY=(sy1.getFloatValue()+sy2.getFloatValue())/2f;
			posX=sx2.getFloatValue()-0.1f;
			defineInsideOutside(posX, posY, posZ);
			mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values,
					grid.dim * 0.85f, grid.dim, grid.x1, grid.y1, grid.z1, null);
		}
		if (flagExport) {
			flagExport = false;
			//mc.saveObj(path+timeStamp()+".obj");
			saveSegments();
		}
		drawData();
		cam.beginHUD();
		noLights();
		drawGui();
		cam.endHUD();
	}

	public void drawData() {
		background(0);
		pushMatrix();
		//cam.apply((PGraphics) g);
		rotateY(PI+rY);
		hint(ENABLE_DEPTH_TEST);
		
		
		float dimX = grid.x2() - grid.x1();
		scale(3 / dimX*50);
		scale(1,-1,1);
		PCamera3D.drawWorldAxis3D(g, 5);
		pointLight(170, 170, 170, 3, 3, 3);
		pointLight(170, 170, 170, -3, -3, -3);
		if (displayMode == 0) {
			noFill();
			stroke(0, 255, 0);
			// mesh.draw((PGraphics3D) g);
			/*beginShape(TRIANGLES);
			for (int i = 0; i < mesh.triangles.length; i += 3) {
				for (int j = 0; j < 3; j++) {
					vertex(mesh.xc[mesh.triangles[i + j]],
							mesh.yc[mesh.triangles[i + j]],
							mesh.zc[mesh.triangles[i + j]]);
				}
			}
			endShape();*/
		}
		noStroke();
		fill(255);
		//mc.draw((PGraphics) g);
		mcPreview.draw(g);
		noFill();
		stroke(0, 0, 255);
		translate(grid.x1, grid.y1, grid.z1);
		scale(grid.dim);
		translate(boundsInside.getCenterX(), boundsInside.getCenterY(),
				boundsInside.getCenterZ());
		box(boundsInside.getDimX(), boundsInside.getDimY(),
				boundsInside.getDimZ());
		popMatrix();
	}

	public void drawGui() {
		noLights();
		hint(DISABLE_DEPTH_TEST);
		fill(10);
		stroke(100);
		rect(0, 0, 100, height);
		gui.display(g);
	}
	// returns a string encoding the current time
	public String timeStamp() {
	  return year() + nf(month(), 2) + nf(day(), 2) + "-" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2);
	}
	public void keyPressed() {
		if (key == 'u') {
			flagUpdateInsideOutside = true;
		}
		if (key == 'd') {
			displayMode++;
			displayMode = displayMode % 3;
		}
		if (key == 'e') {
			flagExport = true;
		}
		if (key == 'r') {
			rY -= PI/4;
		}
	}
}
